
// sys
#include <thread>
#include <vector>
#include <memory>
#include <algorithm>
#include <string.h>
#include <atomic>
// 3rd-party
#include <event2/event.h>
#include <event2/thread.h>
// mine
#include "netbase.h"
#include "listener.h"
#include "connector.h"

namespace {
struct ThreadData {
  std::atomic<event_base*> evbase;
  std::unique_ptr<std::thread> thread;

  ThreadData(void) {
    evbase = nullptr;
  }
};

void EvBaseThread(std::atomic<event_base*>& evbase) {
  // create a base
  evbase.store(event_base_new());
  // add a nothing-to-do event to keep the loop not exiting
  event* ev_work = event_new(evbase.load(), -1, EV_READ | EV_PERSIST, NULL, NULL);
  event_add(ev_work, NULL);
  // run dispatch
  try {
    event_base_dispatch(evbase.load());
  } catch (std::exception& e) {
    net_base::__Internal_Log(e.what(), strlen(e.what()));
  } catch (...) {}
  // clean up everything
  event_free(ev_work);
  event_base_free(evbase.load());
}
}

namespace net_base {
IMPL_TYPE(NetBase) {
  std::vector<std::unique_ptr<ThreadData>> threads; 
  std::atomic<size_t> next_evbase_idx;
  bool init;

  IMPL_NAME() : init(false) {
    next_evbase_idx = 0;
  }
};

NetBase::NetBase(void) : IMPL_INIT() {}

NetBase::~NetBase(void) { IMPL_DELETE; }

void NetBase::Run(uint8_t threads_num) {
  if (IMPL.init) return;

  evthread_use_pthreads();
  for (uint8_t i = 0; i < threads_num; ++i) {
    std::unique_ptr<ThreadData> td(new ThreadData);
    td->thread.reset(new std::thread(EvBaseThread, std::ref(td->evbase))); 
    while (!td->evbase.load());
    IMPL.threads.push_back(std::move(td));
  }
  IMPL.init = true;
}

void NetBase::Shutdown(void) {
  std::for_each(IMPL.threads.begin(), IMPL.threads.end(),
                [](std::unique_ptr<ThreadData>& td) 
                { 
                  event_base_loopbreak(td->evbase.load());
                  td->thread->join(); 
                });
}

event_base* NetBase::NextEvBase(void) {
  if (!IMPL.init) return nullptr;

  event_base* evbase = IMPL.threads[IMPL.next_evbase_idx++]->evbase.load();
  if (IMPL.next_evbase_idx == IMPL.threads.size())
    IMPL.next_evbase_idx = 0;
  return evbase;
}

std::unique_ptr<Listener> NetBase::NewListener(void) {
  if (!IMPL.init) return nullptr;
  return std::unique_ptr<Listener>(new Listener(NextEvBase()));
}

std::unique_ptr<Connector> NetBase::NewConnector(void) {
  if (!IMPL.init) return nullptr;
  return std::unique_ptr<Connector>(new Connector(NextEvBase()));
}
} // namespace net_base

